/**
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef PANDA_RUNTIME_MEM_GC_G1_G1_PAUSE_TRACKER_H
#define PANDA_RUNTIME_MEM_GC_G1_G1_PAUSE_TRACKER_H

#include <cstdint>
#include <array>
#include "libpandabase/macros.h"
#include "libpandabase/utils/time.h"
#include "libpandabase/utils/ring_buffer.h"

namespace panda::mem {

/// Track G1 GC pause goal: pauses sum does not exceed max_gc_time in gc_pause_interval
class G1PauseTracker {
public:
    NO_COPY_SEMANTIC(G1PauseTracker);
    NO_MOVE_SEMANTIC(G1PauseTracker);

    G1PauseTracker(int64_t gc_pause_interval_ms, int64_t max_gc_time_ms);

    ~G1PauseTracker() = default;

    bool AddPauseInNanos(int64_t start_time_ns, int64_t end_time_ns);

    bool AddPause(int64_t start_time_us, int64_t end_time_us);

    /// @return minimum delay for pause from now to achieve pause goal
    int64_t MinDelayBeforePauseInMicros(int64_t now_us, int64_t pause_time_us);

    /// @return minimum delay for maximum allowed pause from now to achieve pause goal
    int64_t MinDelayBeforeMaxPauseInMicros(int64_t now_us);

    class Scope {
    public:
        ~Scope()
        {
            owner_->AddPause(start_time_us_, panda::time::GetCurrentTimeInMicros());
        }

        NO_COPY_SEMANTIC(Scope);
        NO_MOVE_SEMANTIC(Scope);

    private:
        explicit Scope(G1PauseTracker *owner) : owner_(owner), start_time_us_(panda::time::GetCurrentTimeInMicros()) {}

        G1PauseTracker *owner_;
        int64_t start_time_us_;

        friend class G1PauseTracker;
    };

    Scope CreateScope()
    {
        return Scope(this);
    }

private:
    class PauseEntry {
    public:
        PauseEntry() : PauseEntry(0, 0) {}
        PauseEntry(int64_t start_time_us, int64_t end_time_us)
            : start_time_us_(start_time_us), end_time_us_(end_time_us)
        {
            ASSERT(end_time_us_ >= start_time_us_);
        }
        int64_t DurationInMicros() const
        {
            return end_time_us_ - start_time_us_;
        }
        int64_t DurationInMicros(int64_t oldest_interval_time) const
        {
            return GetStartTimeInMicros() > oldest_interval_time ? DurationInMicros()
                                                                 : GetEndTimeInMicros() - oldest_interval_time;
        }
        int64_t GetStartTimeInMicros() const
        {
            return start_time_us_;
        }
        int64_t GetEndTimeInMicros() const
        {
            return end_time_us_;
        }

    private:
        int64_t start_time_us_;
        int64_t end_time_us_;
    };

    int64_t CalculateIntervalPauseInMicros(int64_t now_us);
    void RemoveOutOfIntervalEntries(int64_t now_us);

    // Need to analyze real apps what size is required
    static constexpr int SIZE = 16;
    panda::RingBuffer<PauseEntry, SIZE> pauses_;
    int64_t gc_pause_interval_us_;
    int64_t max_gc_time_us_;
};
}  // namespace panda::mem

#endif  // PANDA_RUNTIME_MEM_GC_G1_G1_PAUSE_TRACKER_H
